package com.jp.fm;

import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.fail;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.easymock.EasyMock;
import org.easymock.IMockBuilder;
import org.junit.After;
import org.junit.Before;

public class EasyMockParent {

	@Before
	protected void setUp() throws Exception {
		createStrictMocks();
	}

	@After
	protected void tearDown() throws Exception {
		verifyMocks();
	}

	/**
	 * Create strict mocks of all required types
	 * @throws IllegalAccessException
	 *             Reflection exceptions.
	 */
	protected void createStrictMocks() throws IllegalAccessException {
		final Field[] fields = this.getClass().getDeclaredFields();
		for (final Field f : fields) {
			if (f.getName().startsWith("mock")) {
				f.setAccessible(true);

				f.set(this, EasyMock.createStrictMock(f.getType()));
			}
		}
	}

	/**
	 * Replays all member mocks.
	 */
	protected void replayMocks() {
		try {
			final Field[] fields = this.getClass().getDeclaredFields();
			for (final Field f : fields) {
				if (f.getName().startsWith("mock")) {
					f.setAccessible(true);
					if (f.getType().isInterface()) {
						EasyMock.replay(f.get(this));
					} else {
						replay(f.get(this));
					}
				}
			}
		} catch (final IllegalAccessException ex) {
			fail();
		}
	}

	/**
	 * Verifies all member mocks.
	 */
	protected void verifyMocks() {
		try {
			final Field[] fields = this.getClass().getDeclaredFields();
			for (final Field f : fields) {
				if (f.getName().startsWith("mock")) {
					f.setAccessible(true);
					if (f.getType().isInterface()) {
						EasyMock.verify(f.get(this));
					} else {
						verify(f.get(this));
					}
				}
			}
		} catch (final IllegalAccessException ex) {
			fail();
		}
	}

	private Object createPartialMock(final Class forClass,
			final String[] methodNames, final Class[] argTypes,
			final boolean mockSetters, final boolean niceMock) {

		final List targetClassMethodList = new LinkedList();

		Class currentForClass = forClass;
		while (currentForClass != null) {
			targetClassMethodList.addAll(Arrays.asList(currentForClass
					.getDeclaredMethods()));
			currentForClass = currentForClass.getSuperclass();
		}

		final List methodNamesWeDoNotWantToMock = new LinkedList();
		methodNamesWeDoNotWantToMock.addAll(Arrays.asList(methodNames));

		final List<Method> methodList = new LinkedList<Method>();
		final Method[] targetClassMethods = (Method[]) targetClassMethodList
				.toArray(new Method[0]);
		for (int i = 0; i < targetClassMethods.length; ++i) {
			// if (targetClassMethods[i].getName().equals(methodName)) {
			if (methodNamesWeDoNotWantToMock.contains(targetClassMethods[i]
					.getName())) {
				// Check arguments
			} else if (targetClassMethods[i].getName().startsWith("set")
					&& !mockSetters) {
				// Do nothing, we do not want to mock these methods
			} else {
				methodList.add(targetClassMethods[i]);
			}
		}

		Object mock = null;

		IMockBuilder builder = EasyMock.createMockBuilder(forClass);
		for (Method method : methodList) {
			if (!Modifier.isFinal(method.getModifiers())) {
				builder.addMockedMethod(method);
			}
		}

		if (niceMock) {
			mock = builder.createNiceMock();
		} else {
			mock = builder.createStrictMock();
		}

		return mock;
	}

	/**
	 * @param forClass
	 * @param methodNames
	 * @return
	 */
	protected Object createPartialMock(final Class forClass,
			final String[] methodNames) {
		return createPartialMock(forClass, methodNames, null, true, false);
	}

	/**
	 * @param forClass
	 * @param methodNames
	 * @param mockSetters
	 * @param niceMock
	 * @return
	 */
	protected Object createPartialMock(final Class forClass,
			final String[] methodNames, final boolean mockSetters,
			final boolean niceMock) {
		return createPartialMock(forClass, methodNames, null, mockSetters,
				niceMock);
	}

	/**
	 * @param forClass
	 * @param methodName
	 * @param argTypes
	 * @param mockSetters
	 * @return
	 */
	protected Object createPartialMock(final Class forClass,
			final String methodName, final Class[] argTypes,
			final boolean mockSetters) {
		return createPartialMock(forClass, new String[] { methodName },
				argTypes, mockSetters, false);
	}

	/**
	 * @param forClass
	 * @param methodName
	 * @param argTypes
	 * @return
	 */
	protected Object createPartialMock(final Class forClass,
			final String methodName, final Class[] argTypes) {
		return createPartialMock(forClass, methodName, argTypes, false);
	}

	/**
	 * @param forClass
	 * @param methodName
	 * @param argTypes
	 * @return
	 */
	protected Object createPartialMockWithSetters(final Class forClass,
			final String methodName, final Class[] argTypes) {
		return createPartialMock(forClass, methodName, argTypes, true);
	}
}
